iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0
自我挑戰組

Effective C++ 讀書筆記系列 第 21

[Day 21] Provide access to raw resources in resource-managing classes

  • 分享至 

  • xImage
  •  

前言

今天的守則又跟第三章開頭介紹的([Day 19] Use objects to manage resources)用object來管理資源有關,延伸下去其他的注意事項,就來看看吧!

我需要取得raw resources!

今天的守則是:

Provide access to raw resources in resource-managing classes

第三章一開始有說我們要用resource-amanging class來管理資源,但實際情況下,有時候我們需要直接取得資源,就必須要思考提供的方式。例如,現在我們用一個smart pointer的物件來管理資源,但其他function需要直接用到pointer指向的物件,我們用前兩天都有用到的例子來舉例:

std::shared_ptr<Investment> pInv(createInvestment());

int daysHeld(const Investment *pi);

此時如果想將資源傳入,可能會想這樣寫:

int days = daysHeld(pInv); // error!

這會無法編譯,因為daysHeld接受的是Investment*這種raw pointer,而pInv的型態則是shared_ptr<Investment>
這要怎麼辦呢?代表這個RAII class─shared_ptr需要提供方法來取得其中的raw pointer。有兩種方式: 1. explicit conversion;2. implicit conversion
在這個case中,shared_ptr有提供explicitconversion的用法─ get(),像這樣:

int days = daysHeld(pInv.get());

透過get(),就能取得raw pointer並傳遞給daysHeld
而這些smart pointer也有提供implicit conversion的方式,他們有overload dereferencing operator來取裡面的raw pointer,例如這樣:

class Investment
{
public:
    bool isTaxFree() const;
};

Investment* createInvestment();
std::shared_ptr<Investment> pi1(createInvestment());
bool taxable1 = !(pi1-> isTaxFree());
bool taxable2 = !((*pi1).isTaxFree());

就可以直接藉由operator->operator*來access到裡面的raw pointer。

除了smart pointer的例子,我們再來看看,現在可能有一個針對font這種資源的RAII class,他有一個C的API:

FontHandle getFont(); // from C API

void releaseFont(FontHandle fh);

class Font // RAII class
{
public:
    explicit Font(FontHandle fh): f(fh){}
    ~Font() {releaseFont(f);}
private:
    FontHandle f; // raw font resource
}

此時,如果我們有很多C API都是針對FontHandle這種raw respirce來做事,我們就會一直需要把Font物件轉成FontHandle,那我們就可以在Font class裡面設計一個explicit conversion,例如也是用get()

class Font
{
public:
    FontHandle get() const {return f;}
}

不過這樣那些C API都是要FontHandle,我們每次使用那些API就都需要多call一個get

void changeFontSize(FontHandle f, int newSize); // C API
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize); // explicit conversion

而有些人就會覺得這樣很麻煩,反而就不想使用Font這個class,這樣就本末倒置了!因為我們原本用Font就是想避免直接用FontHandle可能會有resource leak。為了降低這個風險,我們也可以讓Font提供implicit conversion的function:

class Font
{
public:
    operator FontHandle() const {return f;}
}

這樣那些C API call起來就方便許多:

Font f(getFont());
int newFontSize;
...
changeFontSize(f, newFontSize); // implicit conversion

相對應的缺點就是可能會誤call默默轉型沒發現,如果誤轉型可能會造成FontHandle流落在外被單獨使用。

結論來說,要讓這個RAII class使用implicit或explicit轉型,要視當下的task而定,精髓就是要讓這個介面用起來, 正確使用容易,錯誤使用難。例如雖然explicit使用起來安全,但有可能implicit的使用會讓user更願意去使用RAII class。

最後再釐清一點,有些人可能會覺得RAII class可以取得raw resource是一件違反封裝特性的事,不過可以思考一點是RAII class的設計不是為了封裝,而是為了保證資源的釋放,且視切入角度,像shared_ptr這個class,他其實有好好的封裝了reference-counting的機制,而提供了對raw pointer的存取。總之重點就是─隱藏client不需要看到的,但提供client需要存取的

總結

貼心重點提醒:

  • APIs often require access to raw resources, so each RAII class should offer a way to get at the resource it manages.
  • Access may be via explicit conversion or implicit conversion. In general, explicit conversion is safer, but implicit conversion is more convenient for clients.

第一就是說明這些RAII class需要提供能取得資源的interface,第二則是提醒兩種做法─explicit與implicit conversion都各有優缺點,需要視情況去做對應設計。


上一篇
[Day 20] Think carefully about copying behavior in resource-managing classes
下一篇
[Day 22] Use the same form in corresponding uses of new and delete
系列文
Effective C++ 讀書筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言